namespace ClassyBlog.ExtensionMethods
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;

    using Infrastructure;

    public static class ObjectExtensions
    {
        private static readonly Func<PropertyInfo, bool> readablePropertyfilter
            = p => p.CanRead && !p.GetIndexParameters().Any();

        private static readonly Func<PropertyInfo, bool> writablePropertyFilter
            = p => p.CanWrite && !p.GetIndexParameters().Any();

        [DebuggerStepThrough]
        public static T To<T>(this object instance)
        {
            return (T)instance;
        }

        [DebuggerStepThrough]
        public static T As<T>(this object instance) where T : class
        {
            return instance as T;
        }

        [DebuggerStepThrough]
        public static dynamic Property(this object instance)
        {
            return new PropertyAccessor(instance);
        }

        [DebuggerStepThrough]
        public static TResult Try<TObject, TResult>(
            this TObject instance,
            Func<TResult> action,
            TResult defaultValue = default(TResult))
            where TObject : class
        {
            var result = (instance == null || action == null) ?
                         defaultValue :
                         action();

            return result;
        }

        [DebuggerStepThrough]
        public static IDictionary<string, object> ToDictionary(
            this object instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            var result = instance.GetType()
                                 .GetProperties()
                                 .Where(readablePropertyfilter)
                                 .ToDictionary(
                                 property => property.Name,
                                 property => property.GetValue(
                                     instance, null));

            return result;
        }

        [DebuggerStepThrough]
        public static void Merge<TTo, TFrom>(this TTo instance, TFrom source)
            where TTo : class where TFrom : class
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            var properties = instance.GetType()
                                     .GetProperties()
                                     .Where(writablePropertyFilter)
                                     .ToDictionary(p => p.Name, p => p);

            foreach (var pair in source.ToDictionary()
                                       .Where(pair2 =>
                                           properties.ContainsKey(pair2.Key)))
            {
                properties[pair.Key].SetValue(instance, pair.Value, null);
            }
        }
    }
}